import {GameData} from "./GameData";
import {SyncFrame} from "./options/SyncFrame";

import {PlayerOption} from "./options/PlayerOption";
import {KeyboardType} from "./Enums";
import {Utils} from "../utils/Utils";
import {EntityCollection} from "../entities/basic/EntityCollection";
import {KeyBoardState} from "./KeyBoardState";
import {NumberUtil} from "../utils/NumberUtil";
import {
  ClientFrameMessage,
  ClientMessageJsonBox,
  ClientMessageType,
  FrameRequestMessage,
  GameInitMessage,
  InitMessage,
  ServerMessageJsonBox,
  ServerMessageType,
  SyncFrameMessage
} from "./Messages";
import {Drawer} from "./Drawer";
import {FrameSpeedHolder} from "./FrameSpeedHolder";

export class Game {

  playerId: number = 0;

  gameId: number;

  connection: WebSocket | undefined;

  serverSocketUrl: string;

  gameData: GameData = new GameData();

  /**
   * 每秒有多少逻辑帧
   */
  logicFramePerSeconds = 60;

  targetRenderFrames: number = 120;

  /**
   * 每多少个逻辑帧是一个同步帧
   */
  syncFrameInterval = 3;


  /**
   * 用来存储同步帧,假设syncFrameInterval=0 那么syncFrameOptions[0].id = 0,syncFrameOptions[1].id = 3 以此类推
   */
  syncFrames: Array<SyncFrame> = new Array<SyncFrame>();

  /**
   * 下一次触发更新的时间戳
   */
  nextLogicFrameUpdateTime: number = 0;

  entityCollection: EntityCollection = new EntityCollection(this);

  /**
   * 收集的玩家操作，用于发送下一个同步帧
   */
  nextSyncOption: PlayerOption | undefined;

  keyBoardState: KeyBoardState = new KeyBoardState();

  isGameOver = false;

  canvas: HTMLCanvasElement;
  bufferCanvas: HTMLCanvasElement;

  drawer: Drawer;

  /**
   * 当前同步帧中，玩家是否有操作变化
   */
  hasChange: boolean = false;

  /**
   * 是否停止游戏更新
   */
  gameStop: boolean = false;

  frameHolder: FrameSpeedHolder = new FrameSpeedHolder();

  currentFrame: number = 0;


  //网络帧延时
  delay: number = 0;
  //帧号:时间
  delayMap: Map<number, number> = new Map<number, number>();

  testAction: (x: number, y: number, x2: number, y2: number) => void;

  constructor(serverSocketUrl: string, gameId: number, canvas: HTMLCanvasElement, action: (x: number, y: number, x2: number, y2: number) => void) {
    this.serverSocketUrl = serverSocketUrl;
    this.testAction = action;
    this.gameId = gameId;
    this.canvas = canvas;
    this.bufferCanvas = document.createElement("canvas");
    this.bufferCanvas.width = this.canvas.width;
    this.bufferCanvas.height = this.canvas.height;
    this.drawer = new Drawer(this, this.bufferCanvas);
  }

  async start() {
    console.log("game started");
    this.connectToServer();
    this.listenFromServer();
    await Utils.sleep(2000);
    while (this.playerId == 0 && !this.gameStop) {
      await Utils.sleep(100);
    }
    this.registerKeyboardListener();
    await this.startLoops();
    // setInterval(() => {
    //   console.log(this.currentFrame, this.drawFrame, this.frameHolder.getLogicFramesPerSecond(), this.frameHolder.getRenderFramesPerSecond());
    // }, 1000);
  }

  stopGame() {
    this.gameStop = true;
    console.log("stopgame...");
    this.connection?.close();
  }

  private connectToServer() {
    this.connection = new WebSocket(this.serverSocketUrl);

  }

  private registerKeyboardListener() {
    document.addEventListener("keydown", (evt: KeyboardEvent) => {
      this.handlePlayerOption(KeyboardType.KEYDOWN, evt);
    });

    document.addEventListener("keyup", (evt: KeyboardEvent) => {
      this.handlePlayerOption(KeyboardType.KEYUP, evt);
    });
  }

  private listenFromServer() {
    const conn = this.connection;

    if (conn != null) {

      conn.onopen = async (e: Event) => {
        if (!this.gameStop) {
          console.log("onopen", e);
          await Utils.sleep(1000);
          await this.sendInitMessage();
        }

      };
      conn.onmessage = (s) => {
        if (!this.gameStop) {
          this.handleServerMessage(JSON.parse(s.data));
        }

      };
      conn.onclose = (e) => {
        console.log("onclose", e);
        if (!this.gameStop) {
          this.gameStop = true;
          console.log("game stop because connection is closed");
          // this.keepConnectToServer();
        }

      };
      conn.onerror = (e) => {
        if (!this.gameStop) {
          console.log("onerror", e);
          this.keepConnectToServer();
        }
      };
    }
  }

  private async keepConnectToServer() {
    await Utils.sleep(100);
    await this.connectToServer();
    await Utils.sleep(1000);
    this.listenFromServer();

  }

  private handleServerMessage(serverMessage: ServerMessageJsonBox) {

    if (this.gameStop) {
      return;
    }
    if (serverMessage.messageType == ServerMessageType.GAME_INIT.toString()) {
      const gameInitMessage = JSON.parse(serverMessage.contextJson!) as GameInitMessage;
      this.playerId = gameInitMessage.playerId!;
      const initGameData = gameInitMessage.gameData;
      if (initGameData != undefined) {


        if (initGameData.lastFrame - this.currentFrame > 1) {


          //需要追帧的情况
          this.gameData.lastFrame = initGameData.lastFrame;
          /**执行追帧 批量拉取一批同步帧**/
          const frameIndices: Array<number> = [];
          for (let i = this.currentFrame; i < this.gameData.lastFrame; i++) {
            frameIndices.push(i);
          }
          this.requestFrameForce(frameIndices);
        }

      }

    } else if (serverMessage.messageType == ServerMessageType.SYNC_FRAME) {


      const syncMessage = JSON.parse(serverMessage.contextJson!) as SyncFrameMessage;
      const frames = syncMessage.frames;

      frames?.forEach(f => {
        const sendTime = this.delayMap.get(f.frameIndex!!);
        if (sendTime != undefined) {
          this.delay = Date.now() - sendTime;
        }
        this.delayMap.delete(f.frameIndex!!);
        this.putSyncFrame(f);
      });


    } else {


    }
  }

  private async sendInitMessage() {
    const initMessage = new InitMessage();
    initMessage.gameId = this.gameId;

    const box = new ClientMessageJsonBox();
    box.messageType = ClientMessageType.CLIENT_INIT;
    box.contextJson = JSON.stringify(initMessage);

    try {
      this.connection?.send(JSON.stringify(box));
    } catch (s) {
      await Utils.sleep(1000);
      await this.sendInitMessage();
    }

    //循环等待 直到接收到了playerId
    while (this.playerId == 0 && !this.gameStop) {
      await Utils.sleep(1000);
    }
  }

  private async sendSyncFrameOptionToServer() {
    if (this.gameStop) {
      return;
    }

    const msg = new ClientFrameMessage();
    msg.gameId = this.gameId;
    msg.frameIndex = this.currentFrame;
    //第零帧要创建一个坦克
    const createTank = this.currentFrame == 0;
    msg.option = new PlayerOption(this.keyBoardState, this.isGameOver, createTank);

    const box = new ClientMessageJsonBox();
    box.messageType = ClientMessageType.SYNC_FRAME;
    box.contextJson = JSON.stringify(msg);


    try {
      if (this.delayMap.get(this.currentFrame) == undefined) {
        this.delayMap.set(this.currentFrame, Date.now());
      }
      this.connection?.send(JSON.stringify(box));
    } catch (e) {
      await this.keepConnectToServer();

    }


    this.hasChange = false;


  }

  /**
   * 启动循环
   */
  private async startLoops() {

    await this.logicLoop();
    this.renderLoop();
  }

  private logicLoop = async () => {
    if (this.gameStop) {
      return;
    }
    if (this.shouldUpdateLogic()) {
      if (this.nextLogicFrameUpdateTime == 0) {
        this.nextLogicFrameUpdateTime = Date.now();
      }
      this.nextLogicFrameUpdateTime += this.logicFrameUpdateTimeIntervalMills();
      if (this.isSyncFrame(this.currentFrame)) {
        await this.doSyncFrameOptions();
      }
      this.updateLogic();
      this.currentFrame++;
      this.gameData.lastFrame = Math.max(this.currentFrame, this.gameData.lastFrame);
    }

    /**简易的快速追帧逻辑 如果当前最后一个可以读取的帧大于当前帧，则快速进行下一个循环。**/
    if (this.gameData.lastFrame > this.currentFrame + 1) {
      this.nextLogicFrameUpdateTime = Date.now();
    }

    let waitTime = this.nextLogicFrameUpdateTime - Date.now();

    // if (this.syncFrames.length > 0) {
    //   const syncFrame = this.syncFrames[this.syncFrames.length - 1];
    //   if (syncFrame != null) {
    //     if (this.gameData.lastFrame < syncFrame.frameIndex!) {
    //       waitTime = 0;
    //
    //     }
    //   }
    // }


    setTimeout(this.logicLoop, waitTime < 0 ? 0 : waitTime);
  };

  lastRenderTime: number = 0;
  private renderLoop = () => {
    if (this.gameStop) {
      return;
    }

    this.entityCollection.updateEntitiesDraw();//更新实体的绘制位置
    this.render();//渲染
    this.drawFrame++;

    const passedTime = Date.now() - this.lastRenderTime;
    const sp = 1000 / (this.targetRenderFrames*1.1);//*1.1是因为有延迟 所以可能需要提前提交更新。
    //60帧
    if (passedTime < sp) {
      Utils.sleep(Math.max(0, sp - passedTime)).then(() => {
        window.requestAnimationFrame(this.renderLoop);
      });
    } else {
      window.requestAnimationFrame(this.renderLoop);
    }

    this.lastRenderTime = Date.now();


  };

  drawFrame: number = 0;

  private render() {
    this.drawer.draw();

    //双缓冲 this.drawer里的HTMLCanvasElement是 this.bufferCanvas 因此要复制到主绘板。
    const context = this.canvas.getContext("2d")!;
    context.clearRect(0, 0, this.canvas.width, this.canvas.height);
    context.drawImage(this.bufferCanvas, 0, 0, this.canvas.width, this.canvas.height, 0, 0, this.canvas.width, this.canvas.height);


    this.frameHolder.onRenderFrameUpdate();

  }

  private updateLogic() {

    this.frameHolder.onLogicFrameUpdate();
    this.entityCollection.updateEntities();

  }

  private isSyncFrame(frameId: number): boolean {
    return (frameId % this.syncFrameInterval) == 0;
  }

  /**
   * 获取下一个同步帧
   * @param syncFrameId
   * @private
   */
  private async fullWaitSyncFrameOption(syncFrameId: number): Promise<SyncFrame> {
    if (this.gameStop) {
      return Promise.reject("game stopped");
    }
    let tryCount = 0;
    let index = this.getIndexOfSyncFrameArray(syncFrameId);

    while (true) {
      if (this.gameStop) {
        return Promise.reject("game stopped");
      }
      index = this.getIndexOfSyncFrameArray(syncFrameId);
      if (this.syncFrames.length - 1 >= index) {
        return this.syncFrames[index];
      }
      //可能认为是自己的同步帧没有发送成功 再发送一次
      if (tryCount % 10 == 0) {

        if (!this.hasFrameCanRead() || tryCount % 100 == 0) {
          await this.sendSyncFrameOptionToServer();
        }

        //尝试获取更多的帧 以及直接请求这个帧 可能是服务器拒绝了自己的同步帧请求。
        const frameIndices: Array<number> = [];
        frameIndices.push(syncFrameId);
        for (let i = 1; i < this.gameData.lastFrame - this.currentFrame; i++) {
          frameIndices.push(syncFrameId + i);
        }
        await this.requestFrameForce(frameIndices);
      }
      tryCount++;

      await Utils.sleep(Math.min(tryCount + 1, 100));
    }

  }

  //用于客户端请求限流 避免太多的请求
  lastRequestTime: number = Date.now();

  private async requestFrameForce(frameIndices: Array<number>) {

    if (this.gameStop) {
      return;
    }
    //客户端请求限流 避免太多的请求
    if (Date.now() - this.lastRequestTime < 1000) {
      return;
    }
    this.lastRequestTime = Date.now();


    const msg = new FrameRequestMessage();
    msg.gameId = this.gameId;
    msg.frameIndices = frameIndices;

    const box = new ClientMessageJsonBox();
    box.messageType = ClientMessageType.FRAME_REQUEST;
    box.contextJson = JSON.stringify(msg);


    try {
      this.connection?.send(JSON.stringify(box));
    } catch (e) {
      await this.keepConnectToServer();
    }

  }

  private getIndexOfSyncFrameArray(syncFrameIndex: number): number {

    return (syncFrameIndex - this.firstSyncFrameIndex()) / this.syncFrameInterval;
  }

  private putSyncFrame(f: SyncFrame) {
    const index = this.getIndexOfSyncFrameArray(f.frameIndex!!);
    this.syncFrames[index] = f;
  }

  private shouldUpdateLogic(): boolean {
    return this.nextLogicFrameUpdateTime <= Date.now();
  }

  //两次逻辑帧更新的间隔时间 单位毫秒
  private logicFrameUpdateTimeIntervalMills(): number {
    return NumberUtil.resolved(1000 / this.logicFramePerSeconds);
  }

  /**
   * 进行同步帧操作
   */
  private async doSyncFrameOptions() {

    if (this.gameStop) {
      return;
    }
    //如果已经接收到了后面的帧 则不用发送自己的同步帧了
    if (!this.hasFrameCanRead()) {
      await this.sendSyncFrameOptionToServer();
    }

    const syncFrame = await this.fullWaitSyncFrameOption(this.currentFrame);


    this.entityCollection.updateEntitiesControl(syncFrame.options);

    /**
     * 滑动接收窗口
     */
    while (this.firstSyncFrameIndex() < syncFrame.frameIndex!) {
      this.syncFrames.shift();
    }


  }

  lastFrameCanRead(): number | undefined {
    const length = this.syncFrames.length;
    if (length == 0) {
      return undefined;
    } else {
      const syncFrame = this.syncFrames[length - 1];
      if (syncFrame != undefined) {
        return syncFrame.frameIndex;
      }

    }
    return undefined;
  }

  //有很多帧可以读
  hasFrameCanRead(): boolean {
    const lastFrame = this.lastFrameCanRead();
    const currentFrame = this.currentFrame;
    if (lastFrame == undefined) {
      return false;
    }
    return lastFrame >= currentFrame;
  }

  hasMoreFrameCanRead(): boolean {
    const lastFrame = this.lastFrameCanRead();
    const currentFrame = this.currentFrame;
    if (lastFrame == undefined) {
      return false;
    }
    return lastFrame > currentFrame;
  }

  /**
   * 返回第一个帧的index 如果当前还没有帧 则返回1
   * @private
   */
  private firstSyncFrameIndex(): number {
    const frameIndex = this.syncFrames[0]?.frameIndex;
    if (frameIndex == undefined) {
      return 0;
    }
    return frameIndex;
  }

  private handlePlayerOption(type: KeyboardType, evt: KeyboardEvent) {


    let changed = false;

    if (type == KeyboardType.KEYDOWN) {
      evt.preventDefault();

      switch (evt.key) {
        case "a":
          if (!this.keyBoardState.aPress) {
            changed = true;
            this.keyBoardState.aPress = true;
          }
          break;
        case "s":
          if (!this.keyBoardState.sPress) {
            changed = true;
            this.keyBoardState.sPress = true;
          }
          break;
        case "d":
          if (!this.keyBoardState.dPress) {
            changed = true;
            this.keyBoardState.dPress = true;
          }
          break;
        case "w":
          if (!this.keyBoardState.wPress) {
            changed = true;
            this.keyBoardState.wPress = true;
          }
          break;
        case " ":
          if (!this.keyBoardState.spacePress) {
            changed = true;
            this.keyBoardState.spacePress = true;
          }
          break;
      }

    } else {
      switch (evt.key) {
        case "a":
          if (this.keyBoardState.aPress) {
            changed = true;
            this.keyBoardState.aPress = false;
          }
          break;
        case "s":
          if (this.keyBoardState.sPress) {
            changed = true;
            this.keyBoardState.sPress = false;
          }
          break;
        case "d":
          if (this.keyBoardState.dPress) {
            changed = true;
            this.keyBoardState.dPress = false;
          }
          break;
        case "w":
          if (this.keyBoardState.wPress) {
            changed = true;
            this.keyBoardState.wPress = false;
          }
          break;
        case " ":
          if (this.keyBoardState.spacePress) {
            changed = true;
            this.keyBoardState.spacePress = false;
          }
      }

    }
    this.hasChange = changed;

  }

  getGameSpeed(): number {
    return this.logicFramePerSeconds;
  }

}
